package fr.gdi.android.news.utils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.xmlpull.v1.XmlSerializer;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import android.util.Xml;
import fr.gdi.android.news.DatabaseHelper;
import fr.gdi.android.news.FeedDescription;
import fr.gdi.android.news.IConnectionHeaderService;
import fr.gdi.android.news.importer.AbstractFeedImporterTask;
import fr.gdi.android.news.importer.OpmlFeedImporterTask;
import fr.gdi.android.news.parser.Enclosure;
import fr.gdi.android.news.parser.Feed;
import fr.gdi.android.news.parser.FeedHandler;
import fr.gdi.android.news.parser.Item;
import fr.gdi.android.news.utils.Constants.Preferences;
import fr.gdi.android.news.utils.Constants.Provider;

public final class FeedUtils
{
    static class RemoteConnection implements ServiceConnection {
        private IConnectionHeaderService service;
    
        public RemoteConnection()
        {

        }
    
        public void onServiceConnected(ComponentName className, IBinder binder) 
        {
            service = IConnectionHeaderService.Stub.asInterface(binder);
            synchronized(this) {
                notifyAll();
            }
        }

        public void onServiceDisconnected(ComponentName className) {
            service = null;
        }
    };
    
    private static IConnectionHeaderService getService(Context ctx, Intent intent, boolean forceNew) 
    {
        RemoteConnection connection = new RemoteConnection();
        ctx.getApplicationContext().startService(intent);
        ctx.getApplicationContext().bindService(intent, connection, Context.BIND_AUTO_CREATE);
        
        try
        {
            synchronized (connection)
            {
                while ( connection.service == null ) {
                    connection.wait();
                }
            }
        }
        catch (InterruptedException e)
        {
            Log.e(FeedUtils.class.getName(), "Caught InterruptedException while waiting to obtain remote service");
        }
        
        IConnectionHeaderService service = connection.service;
        
        return service;
    }
    
    private FeedUtils()
    {
    }
    
    public static final List<String> getFeedNames(Context ctx, Integer appWidgetId)
    {
        List<FeedDescription> descs = getFeedDescriptions(ctx, appWidgetId);
        List<String> names = new ArrayList<String>(descs.size());
        for (FeedDescription desc : descs)
        {
            names.add(desc.name);
        }
        return names;
    }
    
    public static String getFeedCsvList(Context ctx, int appWidgetId)
    {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
        String s = prefs.getString((Preferences.WIDGET_PREF_SETTINGS + appWidgetId + "_" + Preferences.FEED_PREF), "");
        return TextUtils.isEmpty(s) ? "" : s;
    }
    
    public static void removeFeed(Context context, int appWidgetId, String name)
    {
        List<FeedDescription> feeds = getFeedDescriptions(context, appWidgetId);
        ArrayList<FeedDescription> copy = new ArrayList<FeedDescription>(feeds);
        for (FeedDescription f : feeds)
        {
            if (TextUtils.equals(f.name, name))
            {
                copy.remove(f);
            }
        }
        String result = FeedUtils.toCsvString(copy);
        FeedUtils.setFeeds(context, null, result.toString());
        
        SQLiteDatabase db = null;
        DatabaseHelper.lock();
        try
        {
            DatabaseHelper helper = new DatabaseHelper(context);
            db = helper.getWritableDatabase();
            
            db.beginTransaction();
            
            String query = "delete from news where FEED_NAME=?";
            String[] params = new String[] { name };
            db.execSQL(query, params);
            
            query = "delete from colors where FEED_NAME=?";
            db.execSQL(query, params);
            
            db.setTransactionSuccessful();
        }
        finally
        {
            db.endTransaction();
            db.close();
            DatabaseHelper.unlock();
        }
    }
    
    public static List<FeedDescription> getFeedDescriptions(Context context, Integer appWidgetId)
    {
        List<FeedDescription> result = new ArrayList<FeedDescription>();
        
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        
        String feedDefs = prefs.getString(Preferences.FEED_PREF, Preferences.DEFAULT_FEED_DEFINITION);
        
        if (feedDefs == null || feedDefs.trim().length() == 0) return result;
        
        final Map<String, FeedDescription> descs = new HashMap<String, FeedDescription>();
        parse(feedDefs, new IParseCallback() {
            public void exec(FeedDescription d)
            {
                descs.put(d.name, d);
            }
        });
        
        if (appWidgetId != null)
        {
            String wf = prefs.getString((Preferences.WIDGET_PREF_SETTINGS + appWidgetId + "_" + Preferences.FEED_PREF), "");
            if (!TextUtils.isEmpty(wf))
            {
                String[] defs = wf.split(",");
                for (String def : defs)
                {
                    if (descs.containsKey(def))
                    {
                        result.add(descs.get(def));
                    }
                }
            }
            
        }
        else
        {
            result.addAll(descs.values());
        }
        
        return result;
    }
    
    public static Feed getMostRecentNews(Context context, FeedDescription fd)
    {
        return getMostRecentNews(context, fd, false);
    }
    
    public static Feed getMostRecentNews(Context context, FeedDescription fd, boolean rethrow)
    {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        try
        {
            Thread.currentThread().setContextClassLoader(FeedUtils.class.getClassLoader());
            
            String url = fd.url;
            if (!TextUtils.isEmpty(url))
            {
                url = url.trim();
                if (TextUtils.indexOf(url, "http://") != 0 && TextUtils.indexOf(url, "https://") != 0)
                {
                    Log.d(Constants.PACKAGE + ".FeedRetriever", "Missing url protocol. Prepending 'http://' prefix.");
                    url = "http://" + url;
                }
            }
            
            /*
            boolean useGoogle = PreferenceUtils.getBooleanPreference(context, Preferences.FETCH_WITH_GREADER);
            if ( useGoogle ) 
            {
                url = "https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=" +
                            URLEncoder.encode(url, "UTF-8");
            }
            */
            
            Feed feed = retrieveFeed(context, url, fd.url);
            
            if ( feed == null ) return null;
            
            /*
            if ( !PreferenceUtils.getBooleanPreference(context, Preferences.USE_REAL_AUTHOR_NAME, null, false) )
            {
                List<Item> entries = feed.getItems();
                for (Item e : entries)
                {
                    String feedTitle = e.getSource().getTitle();
                    e.setAuthor(fd.name);
                }
            }
            else 
            {
                
            }
            */
            return feed;
        }
        catch (Exception e)
        {
            // swallow..
            Log.e(Constants.PACKAGE + ".FeedRetriever", "Error retrieving feed", e);
            if (rethrow)
            {
                throw new RuntimeException("Cannot fetch feed", e);
            }
            return null;
        }
        finally
        {
            Thread.currentThread().setContextClassLoader(loader);
        }
    }
    
    
    
    @SuppressWarnings("all")
    public static Feed retrieveFeed(Context context, String feedUrl, String originalUrl) throws IOException
    {
        Feed feed = null;
        if (!Constants.USE_EMULATOR)
        {
            HttpURLConnection httpcon = null; 
            try
            {
                httpcon = (HttpURLConnection) new URL(feedUrl).openConnection();
                setConnectionProperties(context, httpcon, feedUrl, originalUrl);
                httpcon.setInstanceFollowRedirects(true);
                httpcon.setRequestProperty("User-Agent", "Snw/1.x");

                FeedHandler h = new FeedHandler(context);
                Feed parsedFeed = h.handleFeed(httpcon);
                return parsedFeed;                    
                
            }
            catch ( Exception e )
            {
                Log.e(Constants.PACKAGE, "Unable to fetch feed from: " + feedUrl, e);
            }
            finally 
            {
                
            }
        }
        else
        {
            feed = mockFeed();
        }
        return feed;
    }

    private static void setConnectionProperties(Context context, HttpURLConnection connection, String feedUrl, String originalUrl)
    {
        String componentParts = PreferenceUtils.getStringPreference(context, getAuthPrefKey(originalUrl), null);
        
        if ( !TextUtils.isEmpty(componentParts) )
        {
            String[] parts = componentParts.split("\\/");
            //ComponentName cn = new ComponentName(parts[0], parts[1]);
            
            //Intent intent = new Intent(Constants.Actions.SET_CONNECTION_HEADER).setComponent(cn);
            Intent intent = new Intent();
            intent.setClassName(parts[0], parts[0] + parts[1]);
            setConnectionProperties(context, connection, intent, false);
                
        }
        
        /*
         basic auth--
         String encoding = new sun.misc.BASE64Encoder().encode("username:password".getBytes());
         httpcon.setRequestProperty ("Authorization", "Basic " + encoding);
         
         greader--
         String _AUTHPARAMS="GoogleLogin auth="
         httpcon.setRequestProperty ("Authorization", _AUTHPARAMS + getGoogleAuthKey(_USERNAME, _PASSWORD))
         
         TODO: digest--
         */
    }

    private static void setConnectionProperties(Context context, HttpURLConnection connection, Intent intent, boolean forceNew)
    {
        IConnectionHeaderService service = getService(context, intent, forceNew);
        
        if ( service != null )
        {
            try
            {
                String s = service.getAuthorizationHeader();
                connection.setRequestProperty("Authorization", s);
            }
            catch ( DeadObjectException e ) 
            {
                setConnectionProperties(context, connection, intent, true);
            }
            catch (RemoteException e)
            {
                Log.w(FeedUtils.class.getName(), "Error while calling remote service!", e);
            }
        }
        else 
        {
            Log.w(FeedUtils.class.getName(), "Couldnot obtain remote service!");
        }
    }
    
    public static Feed mockFeed()
    {
        List<Item> entries = new ArrayList<Item>();
        
        for (int u = 0; u < 11; u++)
        {
            Item e = new Item();
            e.setDescription("mock content " + u);
            e.setPubdate(new Date());
            long d = new Date().getTime();
            e.setTitle("mock entry " + u + " " + d % 26 + ": " + d);
            try
            {
                e.setLink(new URL("http://google.com"));
            }
            catch (MalformedURLException e1)
            {
                //swallow
            }
            entries.add(e);
        }
        Feed feed = new Feed();
        feed.setItems(entries);
        return feed;
    }
    
    public static List<String> getAllFeedNames(Context context)
    {
        return getFeedNames(context, null);
    }
    
    public static void setFeeds(Context context, Integer appWidgetId, String feeds)
    {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        Editor editor = prefs.edit();
        String key = appWidgetId == null ? Preferences.FEED_PREF : (Preferences.WIDGET_PREF_SETTINGS + appWidgetId + "_" + Preferences.FEED_PREF);
        editor.putString(key, feeds);
        editor.commit();
    }
    
    public static int getFeedColor(Context context, String name)
    {
        DatabaseHelper.lock();
        SQLiteDatabase db = null;
        Cursor cursor = null;
        int color = -1;
        try
        {
            DatabaseHelper helper = new DatabaseHelper(context);
            db = helper.getReadableDatabase();
            String sql = "select * from colors where " + Provider.Columns.FEED_NAME.toString() + " = ?";
            String[] args = new String[] { name  };
            cursor = db.rawQuery(sql, args);
            if ( cursor.moveToNext() )
            {
                color = cursor.getInt(cursor.getColumnIndex("color"));
            }
        }
        catch ( Exception e )
        {
            Log.e(FeedUtils.class.getName(), "Unable to update feed colors", e);
        }
        finally 
        {
            if ( cursor != null ) cursor.close();
            if ( db != null ) db.close();
            DatabaseHelper.unlock();
        }
        return color;
    }
    
    public static void setFeedColors(Context context, Map<String, Integer> colors)
    {
        DatabaseHelper.lock();
        SQLiteDatabase db = null;
        try
        {
            DatabaseHelper helper = new DatabaseHelper(context);
            db = helper.getWritableDatabase();
            
            db.beginTransaction();
            
            for (String name : colors.keySet())
            {
                Integer color = colors.get(name);
                if ( color != null && color != -1 )
                {
                    String sql = "insert or replace into colors (" + Provider.Columns.FEED_NAME.toString() + ", color) values(?, ?)";
                    Object[] args = new Object[] { name, color };
                    db.execSQL(sql, args);
                }
            }
            
            db.setTransactionSuccessful();
        }
        catch ( Exception e )
        {
            Log.e(FeedUtils.class.getName(), "Unable to update feed colors", e);
        }
        finally 
        {
            db.endTransaction();
            if ( db != null ) db.close();
            DatabaseHelper.unlock();
        }
        
    }
    
    public static void addFeed(Context context, String name, String url)
    {
        name = name.replaceAll(":",  " ").replace(", ", " ");
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        String feedDefs = prefs.getString(Preferences.FEED_PREF, Preferences.DEFAULT_FEED_DEFINITION);
        feedDefs += Preferences.FEED_SEPARATOR + name + ":" + url;
        Editor editor = prefs.edit();
        editor.putString(Preferences.FEED_PREF, feedDefs);
        editor.commit();
    }
    
    public static String toCsvString(List<FeedDescription> feeds)
    {
        return toCsvString(feeds, false);
    }

    public static String toCsvString(List<FeedDescription> feeds, boolean all)
    {
        StringBuffer result = new StringBuffer();
        int count = feeds.size();
        if (all || count > 1)
        {
            if ( count >= 1 )
            {
                result.append(feeds.get(0).getPreferenceString());
            }
            
            for (int i = 1; i < (all ? count : (count - 1)); i++)
            {
                result.append(Preferences.FEED_SEPARATOR);
                result.append(feeds.get(i).getPreferenceString());
            }
        }
        return result.toString();
    }
    
    public static List<FeedDescription> fromCsvString(String s)
    {
        final List<FeedDescription> result = new ArrayList<FeedDescription>();
        
        parse(s, new IParseCallback() {
            public void exec(FeedDescription d)
            {
                result.add(d);
            }
        });
        
        return result;
    }
    
    private static interface IParseCallback
    {
        void exec(FeedDescription d);
    }
    
    private static void parse(String s, IParseCallback callback)
    {
        if (callback == null || s == null || s.trim().length() == 0) return;
        
        String[] array = s.split(Preferences.FEED_SEPARATOR);
        
        for (String feed : array)
        {
            int idx = feed.indexOf(":");
            if (idx < 0) continue;
            String name = feed.substring(0, idx);
            String url = feed.substring(idx + 1, feed.length());
            FeedDescription desc = new FeedDescription(name, url);
            callback.exec(desc);
        }
    }
    
    public static void exportFeeds(Context context, String filename) throws Exception
    {
        /*
        //legacy
        String encoding = "UTF-8";
        StringBuilder content = new StringBuilder("<?xml version='1.0' encoding='").append(encoding).append("' ?>").append(IOUtils.NL).append("<feeds>")
                .append(IOUtils.NL);
        
        List<FeedDescription> descriptions = getFeedDescriptions(context, null);
        for (FeedDescription fd : descriptions)
        {
            content.append("  <feed name='").append(fd.name).append("' url='").append(fd.url).append("'/>").append(IOUtils.NL);
        }
        
        content.append("</feeds>").append(IOUtils.NL);
        IOUtils.writeFile(IOUtils.EXTERNAL_STORAGE, TextUtils.isEmpty(filename) ? "feeds.xml" : filename, content.toString());
        */

        FileOutputStream fileos = null;    
        try
        {
            File file = IOUtils.getFile(IOUtils.EXTERNAL_STORAGE, TextUtils.isEmpty(filename) ? "feeds.opml" : filename);
            if ( file.exists() ) file.delete();
            file.createNewFile();
            fileos = new FileOutputStream(file);
            
            XmlSerializer serializer = Xml.newSerializer();
            serializer.setOutput(fileos, "UTF-8");
            serializer.startDocument(null, Boolean.valueOf(true));
            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
            
            serializer.startTag(null, "opml");
            serializer.attribute(null, "version", "1.0");
            
            SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z");
            
            serializer.startTag(null, "head");
            
            serializer.startTag(null, "title");
            serializer.text("Scrollable News Export");
            serializer.endTag(null, "title");
            
            serializer.startTag(null, "dateCreated");
            serializer.text(format.format(new Date()));
            serializer.endTag(null, "dateCreated");
            
            serializer.endTag(null, "head");
            
            serializer.startTag(null, "body");
            
            List<FeedDescription> descriptions = getFeedDescriptions(context, null);
            for (FeedDescription fd : descriptions)
            {
                serializer.startTag(null, "outline");
                
                serializer.attribute(null, "text", fd.name);
                serializer.attribute(null, "title", fd.name);
                serializer.attribute(null, "xmlUrl", fd.url);
                
                serializer.endTag(null, "outline");
            }
            
            serializer.endTag(null, "body");
            
            serializer.endTag(null, "opml");
            
            serializer.endDocument();
            
            serializer.flush();
        }   
        finally
        {
            if ( fileos != null ) 
            {
                fileos.close();
            }
        }
    }
    
    public static void importFeeds(Activity context, File file, Runnable callback) throws Exception
    {
        try 
        {
            AbstractFeedImporterTask importer = new OpmlFeedImporterTask(context, file, callback);
            importer.execute();
        }
        catch (Exception e)
        {
            Log.e(FeedUtils.class.getName(), "Unable to parse feed definitions", e);
            throw e;
        }
    }

    public static String getAuthPrefKey(String url)
    {
        return "__auth__" + url.replaceAll(":", "_")
                               .replaceAll("/", "_")
                               .replaceAll("\\?", "_")
                               .replaceAll("\\\\", "_")
                               .replaceAll("=", "_")
                               .replaceAll(" ", "_");
    }
    
    public static String getPackagePrefKey(String url)
    {
        return "__pkg__" + url.replaceAll(":", "_")
                               .replaceAll("/", "_")
                               .replaceAll("\\?", "_")
                               .replaceAll("\\\\", "_")
                               .replaceAll("=", "_")
                               .replaceAll(" ", "_");
    }
}
